/**
 * @file hal_emac.c
 * @brief
 *
 * Copyright (c) 2021 Bouffalolab team
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 */

#include "bl702_emac.h"
#include "bl702_glb.h"
#include "hal_emac.h"

#ifndef MSG
#define MSG(a,...)
#endif

#define EMAC_USE_INSIDE_CLOCK (0)
#define TAG                   "EMAC_BD: "

static EMAC_Handle_Type ethHandle;
static EMAC_Handle_Type *thiz = NULL;

/**
 * @brief
 *
 */
static void emac_gpio_init(void)
{
    uint8_t emacPins[] = { GLB_GPIO_PIN_0, GLB_GPIO_PIN_1, GLB_GPIO_PIN_2,
                           GLB_GPIO_PIN_7, GLB_GPIO_PIN_8,
                           GLB_GPIO_PIN_18, GLB_GPIO_PIN_19, GLB_GPIO_PIN_20, GLB_GPIO_PIN_21, GLB_GPIO_PIN_22 };

    GLB_SWAP_EMAC_CAM_Pin(GLB_EMAC_CAM_PIN_EMAC);

    GLB_GPIO_Func_Init(GPIO_FUN_ETHER_MAC, (GLB_GPIO_Type *)emacPins, sizeof(emacPins));
}

/**
 * @brief
 *
 * @param bdt
 * @return int
 */
static uint32_t emac_bd_get_cur_active(EMAC_BD_TYPE_e bdt)
{
    uint32_t bd = 0;

    bd = BL_RD_REG(EMAC_BASE, EMAC_TX_BD_NUM);

    if (bdt == EMAC_BD_TYPE_TX) {
        bd &= EMAC_TXBDPTR_MSK;
        bd >>= EMAC_TXBDPTR_POS;
    }

    if (bdt == EMAC_BD_TYPE_RX) {
        bd &= EMAC_RXBDPTR_MSK;
        bd >>= EMAC_RXBDPTR_POS;
    }

    return bd;
}

/**
 * @brief
 *
 * @param index
 * @return int
 */
static int emac_bd_rx_enqueue(uint32_t index)
{
    BL_Err_Type err = SUCCESS;

    thiz->rxIndexEMAC = index;

    return err;
}

/**
 * @brief
 *
 * @param index
 * @return int
 */
static void emac_bd_rx_on_err(uint32_t index)
{
    /* to do index - 1 */
    // uint32_t tmp_bd = 0;
    // tmp_bd = BL_RD_REG(EMAC_BASE, EMAC_TX_BD_NUM);
    // if (index == tmp_bd) {
    //     index += (tmp_bd - 1);
    // } else {
    //     index -= 1;
    // }
    // MSG("i:%x,csl%x\r\n", 5, thiz->bd[5].C_S_L);
    // MSG("i:%x,csl%x\r\n", 6, thiz->bd[6].C_S_L);
    // MSG("i:%x,csl%x\r\n", 7, thiz->bd[7].C_S_L);
    // MSG("i:%x,csl%x\r\n", 8, thiz->bd[8].C_S_L);
    // MSG("i:%x,csl%x\r\n", 9, thiz->bd[9].C_S_L);
    /* handle error */
    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(RX_OR)) {
        MSG("EMAC RX OR Error at %s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(RX_RE)) {
        MSG("MAC RX RE Error at %s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(RX_DN)) {
        MSG("MAC RX DN Error at %s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(RX_TL)) {
        MSG("MAC RX TL Error at %s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(RX_CRC)) {
        MSG("MAC RX CRC Error at %s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(RX_LC)) {
        MSG("MAC RX LC Error at %s:%d\r\n", __func__, __LINE__);
    }

    thiz->bd[index].C_S_L &= ~0xff;
    /* RX BD is ready for RX */
    thiz->bd[index].C_S_L |= EMAC_BD_FIELD_MSK(RX_E);
}

/**
 * @brief this func will be called in ISR
 *
 * @param index
 * @return int
 */
static int emac_bd_tx_dequeue(uint32_t index)
{
    BL_Err_Type err = SUCCESS;
    EMAC_BD_Desc_Type *DMADesc;

    thiz->txIndexEMAC = index;
    DMADesc = &thiz->bd[thiz->txIndexEMAC];
    /* release this tx BD to SW (HW will do this) */
    DMADesc->C_S_L &= EMAC_BD_FIELD_UMSK(TX_RD);

    return err;
}

/**
 * @brief
 *
 * @param index
 * @return int
 */
static void emac_bd_tx_on_err(uint32_t index)
{
    /* handle error */
    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(TX_UR)) {
        MSG("%s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(TX_RTRY)) {
        MSG("%s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(TX_RL)) {
        MSG("%s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(TX_LC)) {
        MSG("%s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(TX_DF)) {
        MSG("%s:%d\r\n", __func__, __LINE__);
    }

    if (thiz->bd[index].C_S_L & EMAC_BD_FIELD_MSK(TX_CS)) {
        MSG("%s:%d\r\n", __func__, __LINE__);
    }

    thiz->bd[index].C_S_L &= ~0xff;
}

/**
 * @brief
 *
 */
__WEAK void emac_tx_done_callback_app(void)
{
}

/**
 * @brief
 *
 */
static void emac_tx_done_callback(void)
{
    uint32_t index = 0;
    index = emac_bd_get_cur_active(EMAC_BD_TYPE_TX);

    emac_bd_tx_dequeue(index);

    emac_tx_done_callback_app();
}

/**
 * @brief
 *
 */
__WEAK void emac_tx_error_callback_app(void)
{
}

/**
 * @brief
 *
 */
static void emac_tx_error_callback(void)
{
    uint32_t index = 0;

    index = emac_bd_get_cur_active(EMAC_BD_TYPE_TX);

    emac_bd_tx_on_err(index);
}

/**
 * @brief
 *
 */
__WEAK void emac_rx_done_callback_app(void)
{
}

/**
 * @brief
 *
 */
static void emac_rx_done_callback(void)
{
    uint32_t index = 0;

    index = emac_bd_get_cur_active(EMAC_BD_TYPE_RX);

    emac_bd_rx_enqueue(index);

    emac_rx_done_callback_app();
}

/**
 * @brief
 *
 */
__WEAK void emac_rx_error_callback_app(void)
{
}

/**
 * @brief
 *
 */
static void emac_rx_error_callback(void)
{
    uint32_t index;

    index = emac_bd_get_cur_active(EMAC_BD_TYPE_RX);

    emac_bd_rx_on_err(index);

    emac_rx_error_callback_app();
}

/**
 * @brief
 *
 */
__WEAK void emac_rx_busy_callback_app(void)
{
}

/**
 * @brief
 *
 */
static void emac_rx_busy_callback(void)
{
    MSG("EMAC Rx busy at %s:%d\r\n", __func__, __LINE__);
    emac_rx_busy_callback_app();
}

/**
 * @brief
 *
 * @param emac_cfg
 * @return int
 */
int emac_init(emac_device_t *emac_cfg)
{
    EMAC_CFG_Type emacCfg = {
        .recvSmallFrame = ENABLE,           /*!< Receive small frmae or not */
        .recvHugeFrame = DISABLE,           /*!< Receive huge frmae(>64K bytes) or not */
        .padEnable = ENABLE,                /*!< Enable padding for frame which is less than MINFL or not */
        .crcEnable = ENABLE,                /*!< Enable hardware CRC or not */
        .noPreamble = DISABLE,              /*!< Enable preamble or not */
        .recvBroadCast = ENABLE,            /*!< Receive broadcast frame or not */
        .interFrameGapCheck = ENABLE,       /*!< Check inter frame gap or not */
        .miiNoPreamble = ENABLE,            /*!< Enable MII interface preamble or not */
        .miiClkDiv = 49,                    /*!< MII interface clock divider from bus clock */
        .maxTxRetry = 16,                   /*!< Maximum tx retry count */
        .interFrameGapValue = 24,           /*!< Inter frame gap vaule in clock cycles(default 24)*/
        .minFrameLen = 64,                  /*!< Minimum frame length */
        .maxFrameLen = ETH_MAX_PACKET_SIZE, /*!< Maximum frame length */
        .collisionValid = 16,               /*!< Collision valid value */
        .macAddr[0] = 0x18,                 /*!< MAC Address */
        .macAddr[1] = 0xB0,
        .macAddr[2] = 0x09,
        .macAddr[3] = 0x00,
        .macAddr[4] = 0x12,
        .macAddr[5] = 0x34,
    };
    BL_Err_Type err = SUCCESS;

    /* init emac giio */
    emac_gpio_init();

    memcpy(emacCfg.macAddr, emac_cfg->mac_addr, 6);
#if EMAC_USE_INSIDE_CLOCK
    //enable audio clock */
    PDS_Enable_PLL_Clk(PDS_PLL_CLK_48M);
    PDS_Set_Audio_PLL_Freq(AUDIO_PLL_50000000_HZ);

    GLB_Set_ETH_REF_O_CLK_Sel(GLB_ETH_REF_CLK_OUT_INSIDE_50M);
#else
    GLB_Set_ETH_REF_O_CLK_Sel(GLB_ETH_REF_CLK_OUT_OUTSIDE_50M);
#endif
    GLB_AHB_Slave1_Clock_Gate(DISABLE, BL_AHB_SLAVE1_EMAC);
    //GLB_Invert_ETH_RX_CLK(0);
    //GLB_Invert_ETH_TX_CLK(0);
    EMAC_Init(&emacCfg);

    EMAC_Int_Callback_Install(EMAC_INT_TX_DONE_IDX, emac_tx_done_callback);
    EMAC_Int_Callback_Install(EMAC_INT_TX_ERROR_IDX, emac_tx_error_callback);

    EMAC_Int_Callback_Install(EMAC_INT_RX_DONE_IDX, emac_rx_done_callback);
    EMAC_Int_Callback_Install(EMAC_INT_RX_ERROR_IDX, emac_rx_error_callback);

    EMAC_Int_Callback_Install(EMAC_INT_RX_BUSY_IDX, emac_rx_busy_callback);

    CPU_Interrupt_Enable(EMAC_IRQn);

    EMAC_ClrIntStatus(EMAC_INT_ALL);
    EMAC_IntMask(EMAC_INT_ALL, UNMASK);

    //EMAC_Enable();

    return err;
}

/**
 * @brief
 *
 * @param eth_tx_buff
 * @param tx_buf_count
 * @param eth_rx_buff
 * @param rx_buf_count
 * @return int
 */
int emac_bd_init(uint8_t *eth_tx_buff, uint8_t tx_buf_count, uint8_t *eth_rx_buff, uint8_t rx_buf_count)
{
    BL_Err_Type err = SUCCESS;
    thiz = &ethHandle;

    /* init the BDs in emac with buffer address */
    err = EMAC_DMADescListInit(thiz, (uint8_t *)eth_tx_buff, tx_buf_count,
                               (uint8_t *)eth_rx_buff, rx_buf_count);
    return err;
}

/**
 * @brief
 *
 * @param flags
 * @param len
 * @param data_in
 * @return int
 */
int emac_bd_tx_enqueue(uint32_t flags, uint32_t len, const uint8_t *data_in)
{
    BL_Err_Type err = SUCCESS;
    EMAC_BD_Desc_Type *DMADesc;

    DMADesc = &thiz->bd[thiz->txIndexCPU];

    if (FULL_PACKET == flags) {
        // MSG("full packet,len:%d\r\n", len);
        flags = (EMAC_TX_COMMON_FLAGS | EMAC_BD_FIELD_MSK(TX_EOF));
    } else if (NOFULL_PACKET == flags) {
        // MSG("nofull packet\r\n");
        flags = EMAC_TX_COMMON_FLAGS;
    }

    if (DMADesc->C_S_L & EMAC_BD_FIELD_MSK(TX_RD)) {
        /* no free BD, lost sync with DMA TX? */
        err = NORESC;
        // MSG_ERR(TAG"%s:%d\n", __func__, __LINE__);
    } else {
        DMADesc->Buffer = (uint32_t)data_in;
        // MSG("tx q flags:%d,len:%d,data:0x%x\r\n", flags, len, data_in);

        // ARCH_MemCpy_Fast((void *)DMADesc->Buffer, data_in, len);

        DMADesc->C_S_L = flags | (len << BD_TX_LEN_POS);

        /* move to next TX BD */
        if ((++thiz->txIndexCPU) > thiz->txBuffLimit) {
            /* the last BD */
            DMADesc->C_S_L |= EMAC_BD_FIELD_MSK(TX_WR);
            /* wrap back */
            thiz->txIndexCPU = 0;
        }
    }

    return err;
}

/**
 * @brief
 *
 * @param flags
 * @param len
 * @param data_out
 * @return int
 */
int emac_bd_rx_dequeue(uint32_t flags, uint32_t *len, uint8_t *data_out)
{
    BL_Err_Type err = SUCCESS;
    EMAC_BD_Desc_Type *DMADesc;

    DMADesc = &thiz->bd[thiz->rxIndexCPU];

    if (DMADesc->C_S_L & EMAC_BD_FIELD_MSK(RX_E)) {
        /* current RX BD is empty */
        err = NORESC;
        *len = 0;
    } else {
        *len = (thiz->bd[thiz->rxIndexCPU].C_S_L & EMAC_BD_FIELD_MSK(RX_LEN)) >> BD_RX_LEN_POS;

        if (data_out) {
            ARCH_MemCpy_Fast(data_out, (const void *)DMADesc->Buffer, *len);
        }

        /* RX BD can be used for another receive */
        DMADesc->C_S_L |= EMAC_BD_FIELD_MSK(RX_E);

        /* move to next RX BD */
        if ((++thiz->rxIndexCPU) > thiz->rxBuffLimit) {
            /* the last BD */
            DMADesc->C_S_L |= EMAC_BD_FIELD_MSK(RX_WR);
            /* wrap back */
            thiz->rxIndexCPU = thiz->txBuffLimit + 1;
        }
    }

    return err;
}

/**
 * @brief
 *
 * @param phyAddress
 * @return int
 */
int emac_phy_set_address(uint16_t phyAddress)
{
    EMAC_Phy_SetAddress(phyAddress);

    return 0;
}

/**
 * @brief
 *
 * @param fullDuplex
 * @return int
 */
int emac_phy_config_full_duplex(uint8_t fullDuplex)
{
    EMAC_Phy_Set_Full_Duplex(fullDuplex);

    return 0;
}

/**
 * @brief
 *
 * @param phyReg
 * @param regValue
 * @return int
 */
int emac_phy_reg_read(uint16_t phyReg, uint16_t *regValue)
{
    if (EMAC_Phy_Read(phyReg, regValue) != SUCCESS) {
        return -1;
    }

    return 0;
}

/**
 * @brief
 *
 * @param phyReg
 * @param regValue
 * @return int
 */
int emac_phy_reg_write(uint16_t phyReg, uint16_t regValue)
{
    if (EMAC_Phy_Write(phyReg, regValue) != SUCCESS) {
        return -1;
    }

    return 0;
}

int emac_stop(void)
{
    return EMAC_Disable();
}

int emac_start(void)
{
    EMAC_Enable();
    return 0;
}

int emac_start_tx(void)
{
    return EMAC_Enable_TX();
}

int emac_stop_tx(void)
{
    return EMAC_Disable_TX();
}

int emac_start_rx(void)
{
    return EMAC_Enable_RX();
}

int emac_stop_rx(void)
{
    return EMAC_Disable_RX();
}
